﻿using EssentialWebFramework.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace EssentialWebFramework.Common.Middlewares
{
    /// <summary>
    /// 用于拦截暴力枚举的请求（仅适用于单机部署）
    /// </summary>
    public class RequestThrottleMiddleaware
    {
        /**
         *  这些请求由于不知道真实地址，绝大部分会返回 [400 ~ 500) 错误，因此针对此的产生频率拦截即可
         */

        /// <summary>
        /// 缓存 TAG
        /// </summary>
        public const string CACHE_TAG = "ReqThroM";

        /// <summary>
        /// 封禁多长时间
        /// </summary>
        private const int FORBIDDEN_MINUTES = 5;

        /// <summary>
        /// 一个时间周期有多长
        /// </summary>
        private const int TIME_UNIT_SECONDS = 3;

        /// <summary>
        /// 一个时间单位内，最多只能“失败”几次
        /// </summary>
        private const int MAX_TIMES_PER_TIME_UNIT = 10;

        /// <summary>
        /// 组件缓存
        /// </summary>
        private readonly CacheForComponent _cache;

        /// <summary>
        /// next
        /// </summary>
        private RequestDelegate _next;


        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="next"></param>
        /// <param name="distributedCache"></param>
        public RequestThrottleMiddleaware(RequestDelegate next, IDistributedCache distributedCache)
        {
            _cache = new CacheForComponent(CACHE_TAG, distributedCache);
            _next = next;
        }

        /// <summary>
        /// Invoke
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context)
        {
            ILogger logger = context.RequestServices.GetService<ILogger<RequestThrottleMiddleaware>>();

            string ipAddress = CommonUtils.FormatIpAddressForCache(context.Connection.RemoteIpAddress.ToString());
            bool isExist = _cache.TryGetCacheValue(ipAddress, out int remainingTimes);

            #region 根据历史记录拦截

            if(isExist && remainingTimes == 0)
            {
                _cache.SetCacheAbsoluteExpire(ipAddress, 0, new TimeSpan(0, FORBIDDEN_MINUTES, 0));

                logger.LogInformation("{0} 已经被禁止，{1} 分钟后可继续访问", ipAddress, FORBIDDEN_MINUTES);

                context.Response.StatusCode = 403;
                context.Response.Body.Close();
                return;
            }

            #endregion

            await _next(context);

            #region 根据其它中间件的返回值记录历史

            if(400 <= context.Response.StatusCode
                    && context.Response.StatusCode < 500)
            {
                if (isExist)
                {
                    _cache.SetCacheSlideExpire(ipAddress, --remainingTimes, new TimeSpan(0, 0, TIME_UNIT_SECONDS));
                    logger.LogInformation("{0} 可访问次数 -1, 剩余 {1}", ipAddress, remainingTimes);                    
                }
                else
                {
                    _cache.SetCacheSlideExpire(ipAddress, MAX_TIMES_PER_TIME_UNIT - 1, new TimeSpan(0, 0, TIME_UNIT_SECONDS));
                    logger.LogInformation("为 {0} 设置访问限制 {1}", ipAddress, MAX_TIMES_PER_TIME_UNIT);
                }
            }            

            #endregion
        }
    }
}
